// Copyright 2011 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package org.apache.tapestry5.csrfprotection;

import java.security.SecureRandom;

import org.apache.tapestry5.services.ApplicationStateManager;
import org.apache.tapestry5.services.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class contains the logic to create a secure token for cross-site script
 * request forgery protection. It is created for a HTTP session and provides the
 * same token throughout the whole session (per-session paradigm).
 */
public class CsrfTokenProvider {
	/**
	 * The session token.
	 */
	private final String sessionToken;

	public static final String TOKEN_NAME = "t:antiCsrfToken";

	private static final Logger logger = LoggerFactory
			.getLogger(CsrfTokenProvider.class);
	private final SecureRandom secureRandom = new SecureRandom();

	/**
	 * Initializes the secure token that will stay the same for the whole life
	 * cycle of this instance.
	 */
	public CsrfTokenProvider() {
		sessionToken = Long.toString(secureRandom.nextLong());
	}

	/**
	 * Returns the token stored in this instance.s
	 * 
	 * @return
	 */
	public String getSessionToken() {
		return sessionToken;
	}

	/**
	 * This method performs the check of the token. It extracts the current
	 * client token from the request and the current server-side token by
	 * accessing the CsrfTokenProvider instance assigned in this session.
	 * 
	 * @param request
	 * @param applicationStateManager
	 * @return
	 * @throws CsrfException
	 */
	public static boolean checkToken(Request request,
			ApplicationStateManager applicationStateManager)
			throws CsrfException {

		String requestParam = getClientToken(request);
		String serverToken = getServerToken(applicationStateManager);
		LoggerFactory.getLogger("org.apache.tapestry5.csrfprotection").debug(
				"SessionToken: " + serverToken + ", ClientToken: "
						+ requestParam);
		if (serverToken.equals(requestParam))
			return true;
		logger.warn("CSRF Attack detected. Server-Token: "
				+ CsrfTokenProvider.getServerToken(applicationStateManager)
				+ " vs. Client-Token: "
				+ CsrfTokenProvider.getClientToken(request));
		throw new CsrfException("CSRF Attack detected. Invalid client token: "
				+ CsrfTokenProvider.getClientToken(request));
	}

	/**
	 * Extracts the client token form a reequest.
	 * 
	 * @param request
	 *            Request that contains the client token
	 * @return Secure Anti-Csrf token.
	 */
	public static String getClientToken(Request request) {
		return request.getParameter(CsrfTokenProvider.TOKEN_NAME);

	}

	/**
	 * Extracts the server-side token from the current application state.
	 * 
	 * @param applicationStateManager
	 *            The CsrfTokenProvider instance is stored in the session.
	 * @return Secure Anti-CSRF token.
	 */
	public static String getServerToken(
			ApplicationStateManager applicationStateManager) {
		CsrfTokenProvider csrfTokenProvider = applicationStateManager
				.get(CsrfTokenProvider.class);
		return csrfTokenProvider.getSessionToken();
	}
}
